/*
* Calendario-flex
* v1.2.05
* Arcadio Carballares Martin, 2012
* http://www.arcadiocarballares.com 
*/
package es.acm.calendario
{
	
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	import mx.collections.ArrayCollection;
	import mx.core.IFactory;
	import mx.events.FlexEvent;
	
	import spark.components.SkinnableContainer;
	import spark.components.TileGroup;
	
	[Event(name="select", type="es.acm.calendario.DayEvent")]
	[Event(name="deselect", type="es.acm.calendario.DayEvent")]
	[Event(name="change", type="es.acm.calendario.DayEvent")]
	[Event(name="next", type="flash.events.Event")]
	[Event(name="previous", type="flash.events.Event")]
	
	[Bindable]
	public class Calendario extends SkinnableContainer implements ICalendario
	{
		[InstanceType("es.acm.calendario.IDayCalendar")]
		public var dayCalendarClass:IFactory;
		public var isHeaderVisible:Boolean = true;
		public var isHeaderEnabled:Boolean = true;
		public var showYear:Boolean = true;
		
		public static const MOBILE:String = 'mobile';
		public static const FLEX:String = 'flex';
		
		private var initialDay:int;
		private var clazz:*;
		private var indexSelectedDay:int;
		private const ROW_NUMBER:int = 6;
		private const COL_NUMBER:int = 7;
		private const PADDING:int = 0;
		private const TOTAL_DAYS:int = 42;
		
		private var container:TileGroup;
		private var header:HeaderCalendar;
		private var headerWeek:HeaderWeek;
		
		public var selectedDate:Date;
		public var selectedDay:IDayCalendar;
		public var title:String;
		
		
		private var _dayNames:Array=new Array('Su', 'Mo','Tu','We','Th','Fr','Sa');
		public function set dayNames(value:Array):void {
			_dayNames = value;
			invalidateProperties();
		}
		public function get dayNames():Array {
			return _dayNames;
		}
		
		private var _monthNames:Array=new Array('January', 'February', 'March', 'April', 'May',
			'June', 'July', 'August', 'September', 'October', 'November',
			'December');
		public function set monthNames(value:Array):void {
			_monthNames = value;
			invalidateProperties();
		}
		public function get monthNames():Array {
			return _monthNames;
		}
		
		private var _firstWeekDay:int=1;
		public function set firstWeekDay(value:int):void {
			_firstWeekDay = value;
			invalidateProperties();
		}
		public function get firstWeekDay():int {
			return _firstWeekDay;
		}
		
		private var _events:ArrayCollection;
		public function set events(value:ArrayCollection):void {
			_events=value;
			checkEvents();
		}
		public function get events():ArrayCollection {
			return _events;
		}
		
		public function Calendario (selectedDate:Date=null)
		{
			super();
			visible = false;
			addEventListener('next', next);
			addEventListener('previous', previous);
			
			this.selectedDate = selectedDate;
			container = new TileGroup();
			
			addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
			
			// Initialize DayCalendarFactory for each new calendar
			DayCalendarFactory.initResultClass();
		}
		
		// Sometimes is better to disable navigation events 
		public function removeNavigationEvents():void {
			removeEventListener('next', next);
			removeEventListener('previous', previous);
		}
		
		override protected function createChildren():void
		{
			super.createChildren();
			// Header
			if (isHeaderVisible) {
				header = new HeaderCalendar();
				header.enabled = isHeaderEnabled;
				header.visible = isHeaderVisible;
				header.includeInLayout = isHeaderVisible;
				addElement(header);
			}
			
			headerWeek = new HeaderWeek();
			headerWeek.dayNames = dayNames;
			addElement(headerWeek);
			// Body
			addElement(container);
			
		}
		
		override protected function commitProperties():void {
			super.commitProperties();
			if (selectedDate && container.numElements > 0) {
				refresh();
			}
		}
		
		protected function onCreationComplete(event:FlexEvent):void {
			initCalendar();
		}
		
		public function initCalendar():void {
			if (!selectedDate) {
				selectedDate = new Date();
			}
			initialDay = calculateInitialDay(selectedDate);
			build(selectedDate);
		}
		
		public function resize():void {
			headerWeek.top = 0;
			if (isHeaderVisible) {
				header.top = 0;
				headerWeek.top = header.height;
				header.width = container.width;
			}
			headerWeek.width = width - (PADDING*2);
			headerWeek.height = 12;
			headerWeek.setStyle('fontSize',11);
			container.top = headerWeek.height;
			container.width = width - (PADDING*2);
			container.height = height - (PADDING*2) - headerWeek.height;
			if (isHeaderVisible) {
				container.top += header.height;
				container.height -= header.height;	
			}
			container.columnWidth = container.width/COL_NUMBER;
			container.rowHeight = container.height/ROW_NUMBER;
			container.horizontalGap=0;
			container.verticalGap=0;
		}
		
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			resize();
		}
		
		override protected function measure():void {
			super.measure();
		}
		
		public function build(date:Date, update:Boolean=false):void {
			// trace('build ' + update + '!');
			title = getMonthLabel(date.month);
			if (showYear) {
				title += ' ' + date.fullYear;
			}
			
			if (isHeaderVisible) {
				header.title.text = title;
			}
			
			if (!update) {
				container.removeAllElements();
			}
			
			var daysMonth:int = calculateDaysMonth(date);
			var day:IDayCalendar;
			var index:int=0;
			
			initialDay = calculateInitialDay(date);
			
			// Previous month days
			if (hasEmptyPreviousDays(initialDay + firstWeekDay)) {
				var preDate:Date = new Date(date.fullYear,date.month-1);
				var preMonth:int = calculateDaysMonth(preDate);
				
				for (var i:int=preMonth-initialDay-firstWeekDay+1;i<=preMonth;i++) {
					if (!update) {
						day=DayCalendarFactory.getInstance(dayCalendarClass,clazz);
						//day=type.getInstance() as IDayCalendar;
						container.addElement(day);
					} else {
						day=container.getElementAt(index) as IDayCalendar;
						day.events = new ArrayCollection();
					}
					day.label.text = ''+i;
					day.setDisabled();
					index++;
				}
			}
			
			// Actual month
			for (var j:int=1;j<=daysMonth;j++) {
				if (!update) {
					day=DayCalendarFactory.getInstance(dayCalendarClass,clazz);
					day.addEventListener(MouseEvent.CLICK, onDayClick);
					container.addElement(day);
				} else {
					day=container.getElementAt(index) as IDayCalendar;
					day.events = new ArrayCollection();
				}
				day.date = new Date(selectedDate.fullYear, selectedDate.month, j); 
				day.label.text = ''+j;
				day.setNormal();
				index++;
			}
			
			// Next month
			for (var k:int=1;k<=TOTAL_DAYS-daysMonth-initialDay-firstWeekDay;k++) {
				if (!update) {
					day=DayCalendarFactory.getInstance(dayCalendarClass,clazz);
					container.addElement(day);
				} else {
					day=container.getElementAt(index) as IDayCalendar;
					day.events = new ArrayCollection();
				}
				day.label.text = ''+k;
				day.setDisabled();
				index++;
			}
			
			selectedDate = date;
			checkEvents();
			
			var todayDate:Date = new Date();
			if (todayDate.fullYear == selectedDate.fullYear && todayDate.month == selectedDate.month) {
				var today:IDayCalendar = container.getElementAt(new Date().date - 1 + initialDay + firstWeekDay) as IDayCalendar;
				if (today.events.length > 0) {
					today.setActive();
				} else {
					today.setTodayNormal();
				}
			}
			
			// Update selected day
			selectedDay = getSelectedDay(selectedDate);
			visible = true;
		}
		
		public function refresh():void {
			headerWeek.dayNames = dayNames;
			headerWeek.invalidateProperties();
			build(selectedDate, true);
		}
		
		private function calculateDaysMonth(date:Date):int {
			var days:int;
			switch (date.month) {
				case 0:
					days=31; // January
					break;
				case 1:
					days=isLeap(date.fullYear); // February
					break;
				case 2:
					days=31; // March
					break;
				case 4:
					days=31; // May
					break;
				case 6:
					days=31; // July
					break;
				case 7:
					days=31; // August
					break;
				case 9:
					days=31; // October
					break;
				case 11:
					days=31; // December
					break;
				default:
					days=30; // 30 days months
					break;
			}
			return days;
		}
		
		private function isLeap(year:int):int {
			if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
				return 29;
			} else {
				return 28;
			}
		}
		
		private function calculateInitialDay(date:Date):int {
			var myDate:Date=new Date(date.fullYear,date.month,0);
			return myDate.getDay();
		}
		
		private function hasEmptyPreviousDays(day:int):Boolean {
			var result:Boolean = false;
			if (day != 0) {
				result = true;
			}
			return result;
		}
		
		private function getMonthLabel(month:int=-1):String {
			var result:String;
			if (month != -1) {
				result = monthNames[month];
			} else {
				result = monthNames[selectedDate.month];
			}
			return result;
		}
		
		private function next(event:Event):void {
			nextMonth();
		}
		
		private function previous(event:Event):void {
			previousMonth();
		}
		
		public function nextMonth():void {
			var date:Date = new Date(selectedDate.fullYear, selectedDate.month);
			date.month += 1;
			build(date, true);
		}
		
		public function previousMonth():void {
			var date:Date = new Date(selectedDate.fullYear, selectedDate.month);
			date.month -= 1;
			build(date, true);
		}
		
		private function onDayClick(event:MouseEvent):void {
			var day:IDayCalendar= event.currentTarget as IDayCalendar;
			var evt:DayEvent = new DayEvent(DayEvent.SELECT);
			evt.data = day;
			
			selectedDate = day.date;
			dispatchEvent(evt);
		}
		
		public function checkEvents():void {
			// trace('checkEvents ' + events + '!');
			for each (var item:IDayData in events) {
				if (item.date.getMonth()==selectedDate.month && 
					item.date.getFullYear()==selectedDate.fullYear) {
					var day:IDayCalendar = container.getElementAt(item.date.date -1 + initialDay + firstWeekDay) as IDayCalendar;
					if (item.enabled) {
						day.addEvent(item);
						day.enabled=true;
					} else {
						day.enabled=false;
					}	
				}
			}
		}
		
		public function setDayCalendarClass(clazz:Class):void {
			this.clazz = clazz;
		}
		
		private function getSelectedDay(date:Date):IDayCalendar {
			indexSelectedDay = date.date - 1 + initialDay + firstWeekDay;
			return container.getElementAt(indexSelectedDay) as IDayCalendar;
		}
		
		public function nextSelectedDay():void
		{
			if (indexSelectedDay < container.numElements) {
				indexSelectedDay++;
				selectedDay = container.getElementAt(indexSelectedDay) as IDayCalendar;
			}
		}
		
		public function previousSelectedDay():void
		{
			if (indexSelectedDay > 0) {
				indexSelectedDay--;
				selectedDay = container.getElementAt(indexSelectedDay) as IDayCalendar;
			}
		}
	}
}